跳到主要内容

react Hooks的一些理解

hooks出来也挺久了,断断续续也用了一段时间,是时候写点文档之类的东西来补充一下认知了,因为我发现在写文档的时候经常能发现之前发现不了的盲点,帮助最近深入理解。

hooks有两个强依赖:1、function组件;2、setXxx,其中set后的第一个字母必须大写;

  1. 从最简单的开始吧:

    useState,总的来说这个可以理解为class组件的setState

    import React, { useState } from 'react';

    export default FunctionTest = () => {
    const [ value, setValue ] = useState(0);
    const add = () => {
    setValue(val+=1);
    }
    return (
    <div>
    <p>{value}</p>
    <button onClick={add}>add</button>
    </div>
    )
    }

    这样每次点击add按钮数值就会加一,这有个问题是value和setValue必须是在数组里声明,第一位是值,第二位是方法;我之前看过一些解析的说为什么用数组而不是对象,主要是为了命名方便,如果是对象那么value和setValue的值名字就是固定了,这显然是很不灵活的方式;

    还有一个需要注意的地方是如果value的值是引用类型而不是基础类型的话,不修改引用是无法触发rerender的,比如以下例子就会没有效果;

    const [ arr, setArr ] = useState([1]);
    setArr(arr.push(2));

    这样是无法触发rerender的,就是说即使调用了这个方法,你的页面也不会有更新;因为是在原数组进行的操作,react默认引用地址不发生改变就不会去rerender,这个是和class组件的一个重大区别,因为class组件中只要调用了setSate,即使什么都不做也会触发render,function组件中是行不通的,不管是对象还是数组,想触发rerender就必须去set返回一个新的引用。

  2. 第二个很常用的就是useEffect。

    import React, { useEffect, useState } from 'react';
    export default FunEffect = () => {
    const [ value, setValue ] = useState(0);
    useEffect(() => {
    // 模拟一个延迟加载
    setTimeout(() => {
    const data = 100;
    setValue(data);
    }, 100)
    }, [])
    return(
    <div>{value}</div>
    );
    };

    这里模拟了一个100毫秒的延迟之后加载val的场景,所以useEffect可以看做是componentDidMount,componentDidUpdate和componentWillUnmount的结合,怎么来区分呢?那就要靠第二个参数的传入值了。如果传入一个[],就代表这个副作用和任何值都不关联,就可以认为这个副作用 === componentDidMount,但是如果我们传了某个值,他就和这个值产生了关联。

    useEffect(() => {
    // 模拟一个延迟加载
    setTimeout(() => {
    const data = 100;
    setValue(data);
    }, 100)
    }, [value]);

    那么你会发现不管你在别的地方如何setValue,最终你会得到value的值为100;

    如果我们用componentDidUpdate来模拟的话应该是这样的:

    componentDidUpdate(prevProps, prevState){
    if(prevState.value !== this.state.value){
    // 我就不写时间延迟了,加上也是一样的
    this.setState({ value: 100});
    }
    }

    那么如果我们数组传入的是一个引用类型的值呢?react会做一层浅比较,引用的值变了依旧会触发副作用;

    最后就是如何模拟componentWillUnmount,useEffect有一个return的可选值,这个return就是让你在组件销毁的时候要做的事情:

    // 偷懒借用一个官方的例子
    useEffect(() => {
    function handleStatusChange(status) {
    setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
    });

    如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它

    注意:effect不能使用async方式

    // 这样式错误的,会导致react直接报错
    useEffect(async() => {
    const data = await xxx();
    }, [])

    正确的方式是这样的:

    useEffect(() => {
    getFeachData();
    }, []);
    const getFeachData = async () => {
    const feachData = await xxx();
    setXXX();
    }
  3. 基础hooks最后一个是useContext

    这个说实话我实际项目中没用到过,所以也只能搬一点官网之类的教程来说了:

    const value = useContext(MyContext);

    接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

    当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memoshouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

    这个呢,就比较像class中的context,但是很遗憾我class组件中也基本没用过,所以就只能粗略说下。

    其实说起来也很简单的,应用场景就是跨组件传值的时候,把想要传下去的数据用Provider包裹,那么他的所有后代组件都能获取到这个数据,然后祖代的数据更新会触发到所有引用该数据的子组件的rerender。

  4. 额外hooks不会都说,只说我用过的,怕误导人,首先是useCallback

    const memoizedCallback = useCallback(
    () => {
    doSomething(a, b);
    },
    [a, b],
    );

    官网的例子很简单,但是经验告诉我们,看起来简单的用法会在某个不知名的地方给与我们沉重的打击,我感觉useCallback和useMemo就是很真实的案例。

    首先看到用法类似与副作用,会传入一些值,这些值也和副作用类似,是数组的数据变化会引起callback中的数据变化。就是说在callback中用的数据要在数组中传入,否则你获取的就是他的“快照”,这个快照让我理解了一段时间才明白,就是类似与shouldComponentUpdate中的prevState,就是上一次的值。举个例子:

    const [ val, setVal ] = useState({ def: { val: 1 } });
    const [ test, setTest ] = useState(0);
    const add = useCallback(() => {
    console.log(test);
    const nextVal = Object.assign({}, {def: { val: val.def.val + 1 }});
    setVal(nextVal);
    }, [val]);

    const testFun = () => {
    setTest(test+1)
    }

    return (
    <div className="App">
    <header className="App-header">
    <p>
    Edit <code>src/App.js</code> and save to reload.
    </p>
    <p>{val.def.val}</p>
    <button onClick={add}>add</button>
    <button onClick={testFun}>test</button>
    </header>
    </div>
    );

    类似这样 我们随便点几下test按钮,然后点add的时候发现第一次打印的test是0,之后才变化,这样的做法官方说是为了性能,减少不必要的rerender,但是确实增加很多理解成本。而且这个只是在本组件中,但是实际中会有很多父子组件的交互,并且可能很多时候是class组件和function组件的混合,这种情况会有一些反直觉的问题。

    具体可以看这里

    然后useMemo和useCallback比价类似,官方也说了:

    useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

    基本就是一个返回function,一个返回值的区别;

  5. 还有就是useRef

    const refContainer = useRef(initialValue);

    可以参见我的这篇文章;

其余的我没有实际用过,也没反向有什么特殊的坑,就暂时不介绍了,怕误人子弟(逃。

Loading Comments...